/*
 * MIT License
 *
 * Copyright (c) 2023-present, tangli
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package tony.test.redis.aspect

import jakarta.annotation.Resource
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.MethodOrderer
import org.junit.jupiter.api.Order
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestMethodOrder
import org.junit.jupiter.api.assertNull
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource
import org.springframework.boot.test.context.SpringBootTest
import tony.core.utils.asTo
import tony.redis.RedisManager
import tony.test.redis.TestRedisApplication
import tony.test.redis.util.SimpleObj
import tony.test.redis.util.TestIntEnum
import tony.test.redis.util.TestStringEnum
import java.math.BigDecimal

/**
 * JacksonRedisCacheAspect 切面功能测试
 *
 * 覆盖基础类型、对象、枚举、List、Map、缓存命中、失效、key 生成、类型转换、过期、复杂表达式等场景
 *
 * @author tony
 * @date 2025/07/01 17:00
 */
@SpringBootTest(classes = [TestRedisApplication::class])
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
@DisplayName("JacksonRedisCacheAspect 切面功能测试")
class JacksonRedisCacheAspectTests {

    @Resource
    private lateinit var service: JacksonRedisCacheAspectService

    companion object {
        @JvmStatic
        fun cacheCases() = listOf(
            arrayOf("bool", true, { a: Any?, b: Any? -> assertEquals(a, b) }),
            arrayOf("int", 123, { a: Any?, b: Any? -> assertEquals(a, b) }),
            arrayOf("long", 123456789L, { a: Any?, b: Any? -> assertEquals(a, b) }),
            arrayOf("bigdecimal", BigDecimal("123.45"), { a: Any?, b: Any? -> assertEquals(a, b) }),
            arrayOf("string", "hello", { a: Any?, b: Any? -> assertEquals(a, b) }),
            arrayOf("obj", SimpleObj("tony", 18), { a: Any?, b: Any? -> assertEquals(a, b) }),
            arrayOf("intEnum", TestIntEnum.ACTIVE, { a: Any?, b: Any? -> assertEquals(a, b) }),
            arrayOf("stringEnum", TestStringEnum.YES, { a: Any?, b: Any? -> assertEquals(a, b) }),
            arrayOf("list", listOf(1, 2, 3), { a: Any?, b: Any? -> assertEquals(a, b) }),
            arrayOf("map", mapOf("a" to 1, "b" to 2), { a: Any?, b: Any? -> assertEquals(a, b) })
        )
    }

    @Order(1)
    @ParameterizedTest
    @MethodSource("cacheCases")
    @DisplayName("缓存命中与类型一致性")
    fun testCacheable(key: String, value: Any?, assertBlock: (Any?, Any?) -> Unit) {
        // set: key, value
        val result = when (value) {
            is Boolean? -> service.cacheBoolean(key, value)
            is Int? -> service.cacheInt(key, value)
            is Long? -> service.cacheLong(key, value)
            is BigDecimal? -> service.cacheBigDecimal(key, value)
            is String? -> service.cacheString(key, value)
            is SimpleObj? -> service.cacheObj(key, value)
            is TestIntEnum? -> service.cacheIntEnum(key, value)
            is TestStringEnum? -> service.cacheStringEnum(key, value)
            is List<*>? -> service.cacheList(key, value.asTo())
            is Map<*, *>? -> service.cacheMap(key, value.asTo())
            else -> throw IllegalArgumentException("不支持的类型: ${value.javaClass}")
        }
        assertBlock(value, result)
        // get: key, value（必须与 set 完全一致，才能命中缓存）
        val cached = when (value) {
            is Boolean? -> service.cacheBoolean(key, value)
            is Int? -> service.cacheInt(key, value)
            is Long? -> service.cacheLong(key, value)
            is BigDecimal? -> service.cacheBigDecimal(key, value)
            is String? -> service.cacheString(key, value)
            is SimpleObj? -> service.cacheObj(key, value)
            is TestIntEnum? -> service.cacheIntEnum(key, value)
            is TestStringEnum? -> service.cacheStringEnum(key, value)
            is List<*>? -> service.cacheList(key, value.asTo())
            is Map<*, *>? -> service.cacheMap(key, value.asTo())
            else -> throw IllegalArgumentException("不支持的类型: ${value.javaClass}")
        }
        assertBlock(value, cached)
    }

    @Order(2)
    @Test
    @DisplayName("缓存失效（Evict）")
    fun testCacheEvict() {
        val key = "evictKey"
        val value = "toBeEvicted"
        service.cacheString(key, value)
        val redisKey = "cacheKeyPrefix:$key:$value"
        assertEquals(value, RedisManager.values.get<String>(redisKey))
        service.evictCache(key, value)
        assertNull(RedisManager.values.get<String>(redisKey))
    }

    @Order(3)
    @Test
    @DisplayName("缓存过期（expire）功能")
    fun testCacheExpire() {
        val key = "expireKey"
        val value = "expireValue"
        service.cacheWithExpire(key, value)
        val redisKey = "cacheKeyPrefix:$key:$value"
        assertEquals(value, RedisManager.values.get<String>(redisKey))
        Thread.sleep(1100)
        assertNull(RedisManager.values.get<String>(redisKey))
    }

    @Order(4)
    @Test
    @DisplayName("复杂表达式 key 生成与命中")
    fun testComplexExpression() {
        val order = JacksonRedisCacheAspectService.Order(
            id = 1001L,
            user = JacksonRedisCacheAspectService.User(99L, "张三"),
            items = listOf("apple", "banana")
        )
        val expected = "order:1001:99:apple"
        val result = service.cacheOrder(order)
        assertEquals(expected, result)
        // 再次调用，命中缓存
        val cached = service.cacheOrder(order)
        assertEquals(expected, cached)
    }

    @Order(5)
    @Test
    @DisplayName("可重复Evict及复杂表达式失效")
    fun testMultiEvict() {
        val key = "multiEvictKey"
        val v1 = "v1"
        val v2 = "v2"
        service.cacheString(key, v1)
        service.cacheString(key, v2)
        val redisKey1 = "cacheKeyPrefix:$key:$v1"
        val redisKey2 = "cacheKeyPrefix:$key:$v2"
        assertEquals(v1, RedisManager.values.get<String>(redisKey1))
        assertEquals(v2, RedisManager.values.get<String>(redisKey2))
        service.evictMulti(key, v1, v2)
        assertNull(RedisManager.values.get<String>(redisKey1))
        assertNull(RedisManager.values.get<String>(redisKey2))
    }

    @Order(6)
    @Test
    @DisplayName("RedisCacheableMap 缓存功能 - 字符串类型")
    fun testCacheableMapString() {
        val key = "testKey"
        val hashKey = "testHashKey"
        val value = "testValue"

        // 第一次调用，应该执行方法并缓存
        val result1 = service.cacheMapString(key, hashKey, value)
        assertEquals(value, result1)

        // 验证缓存是否存在
        val redisKey = "mapCache:$key"
        val redisHashKey = "hash:$hashKey"
        assertEquals(value, RedisManager.maps.get<String>(redisKey, redisHashKey))

        // 第二次调用，应该从缓存获取
        val result2 = service.cacheMapString(key, hashKey, value)
        assertEquals(value, result2)
    }

    @Order(7)
    @Test
    @DisplayName("RedisCacheableMap 缓存功能 - 整数类型")
    fun testCacheableMapInt() {
        val key = "intKey"
        val hashKey = "intHashKey"
        val value = 42

        val result1 = service.cacheMapInt(key, hashKey, value)
        assertEquals(value, result1)

        val redisKey = "mapCache:$key"
        val redisHashKey = "hash:$hashKey"
        assertEquals(value, RedisManager.maps.get<Int>(redisKey, redisHashKey))

        val result2 = service.cacheMapInt(key, hashKey, value)
        assertEquals(value, result2)
    }

    @Order(8)
    @Test
    @DisplayName("RedisCacheableMap 缓存功能 - 对象类型")
    fun testCacheableMapObj() {
        val key = "objKey"
        val hashKey = "objHashKey"
        val value = SimpleObj("张三", 25)

        val result1 = service.cacheMapObj(key, hashKey, value)
        assertEquals(value, result1)

        val redisKey = "mapCache:$key"
        val redisHashKey = "hash:$hashKey"
        val cachedObj = RedisManager.maps.get<SimpleObj>(redisKey, redisHashKey)
        assertEquals(value.name, cachedObj?.name)
        assertEquals(value.age, cachedObj?.age)

        val result2 = service.cacheMapObj(key, hashKey, value)
        assertEquals(value, result2)
    }

    @Order(9)
    @Test
    @DisplayName("RedisCacheableMap 缓存功能 - 复杂表达式")
    fun testCacheableMapComplex() {
        val user = JacksonRedisCacheAspectService.User(100L, "李四")
        val order = JacksonRedisCacheAspectService.Order(
            id = 2001L,
            user = user,
            items = listOf("item1", "item2")
        )

        val expected = "user:100:order:2001"
        val result1 = service.cacheMapComplex(user, order)
        assertEquals(expected, result1)

        // 验证缓存键和hash键的生成
        val redisKey = "userCache:100"
        val redisHashKey = "order:2001:李四"
        assertEquals(expected, RedisManager.maps.get<String>(redisKey, redisHashKey))

        // 再次调用，命中缓存
        val result2 = service.cacheMapComplex(user, order)
        assertEquals(expected, result2)
    }

    @Order(10)
    @Test
    @DisplayName("RedisCacheMapEvict 缓存失效功能")
    fun testCacheMapEvict() {
        val key = "evictKey"
        val hashKey = "evictHashKey"
        val value = "toBeEvicted"

        // 先缓存数据
        service.cacheMapString(key, hashKey, value)
        val redisKey = "mapCache:$key"
        val redisHashKey = "hash:$hashKey"
        assertEquals(value, RedisManager.maps.get<String>(redisKey, redisHashKey))

        // 执行失效操作
        service.evictMapCache(key, hashKey)
        assertNull(RedisManager.maps.get<String>(redisKey, redisHashKey))
    }

    @Order(11)
    @Test
    @DisplayName("RedisCacheMapEvict 复杂表达式失效功能")
    fun testCacheMapEvictComplex() {
        val user = JacksonRedisCacheAspectService.User(200L, "王五")
        val order = JacksonRedisCacheAspectService.Order(
            id = 3001L,
            user = user,
            items = listOf("item3", "item4")
        )

        // 先缓存数据
        service.cacheMapComplex(user, order)
        val redisKey = "userCache:200"
        val redisHashKey = "order:3001:王五"
        assertNotNull(RedisManager.maps.get<String>(redisKey, redisHashKey))

        // 执行失效操作
        service.evictMapCacheComplex(user, order)
        assertNull(RedisManager.maps.get<String>(redisKey, redisHashKey))
    }

    @Order(12)
    @Test
    @DisplayName("可重复 RedisCacheMapEvict 失效功能")
    fun testMultiCacheMapEvict() {
        val key1 = "multiKey1"
        val hashKey1 = "multiHashKey1"
        val value1 = "value1"
        val key2 = "multiKey2"
        val hashKey2 = "multiHashKey2"
        val value2 = "value2"

        // 先缓存两个数据
        service.cacheMapString(key1, hashKey1, value1)
        service.cacheMapString(key2, hashKey2, value2)

        val redisKey1 = "mapCache:$key1"
        val redisHashKey1 = "hash:$hashKey1"
        val redisKey2 = "mapCache:$key2"
        val redisHashKey2 = "hash:$hashKey2"

        assertEquals(value1, RedisManager.maps.get<String>(redisKey1, redisHashKey1))
        assertEquals(value2, RedisManager.maps.get<String>(redisKey2, redisHashKey2))

        // 执行多重失效操作
        service.evictMultiMapCache(key1, hashKey1, key2, hashKey2)

        // 验证两个缓存都被删除
        assertNull(RedisManager.maps.get<String>(redisKey1, redisHashKey1))
        assertNull(RedisManager.maps.get<String>(redisKey2, redisHashKey2))
    }
}
